<template>
  <div ref="threeContainer" class="three-container">
    <div id="box"></div>
  </div>
</template>

<script>
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";

export default {
  name: "LinuxThree",
  data() {
    return {
      delta: 0,
      time: 0,
      vs: {},
      fs: {},
      mesh: {},
      tex: {},
      mat: {},
      texture_loader: null,
      scene: null,
      camera: null,
      renderer: null,
      clock: null,
      controls: null,
      particles: [],
      particles_smoke_a: [],
      particles_grass_a: [],
      particles_emmiter: [],
      wind: {
        x: 0.002,
        y: 0,
        z: 0,
      },
    };
  },
  mounted() {
    this.initThreeJS();
  },
  beforeDestroy() {
    // 清理Three.js资源
    this.cleanupThreeJS();
  },
  methods: {
    initThreeJS() {
      this.initBasicSetup();
      this.initTextures();
      this.initShaders();
      this.initParticles();
      this.setupScene();
      this.animate();
    },
    initBasicSetup() {
      const DOM = document.querySelector("#box");
      const width = DOM.clientWidth;
      const height = DOM.clientHeight;

      // 初始化渲染器
      this.renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true,
      });
      this.renderer.setClearColor(0xb9eeff, 1);
      this.renderer.setPixelRatio(window.devicePixelRatio);
      this.renderer.setSize(width, height, false);
      DOM.appendChild(this.renderer.domElement);

      // 初始化相机
      this.camera = new THREE.PerspectiveCamera(
        45,
        width / height,
        0.05,
        100000
      );
      this.camera.position.set(5, 5, 5);

      // 初始化场景
      this.scene = new THREE.Scene();
      const pl1 = new THREE.PointLight(0xfee3b1, 2);
      pl1.position.set(-20, 20, 20);
      this.scene.add(pl1);

      // 初始化控制器
      this.controls = new OrbitControls(this.camera, this.renderer.domElement);
      this.controls.target.set(0, 0.5, 0);

      // 初始化时钟
      this.clock = new THREE.Clock();

      // 初始化纹理加载器
      this.texture_loader = new THREE.TextureLoader();
    },
    initTextures() {
      this.tex = {
        smoke: this.texture_loader.load(
          ""
        ),
        fire: this.texture_loader.load(
          ""
        ),
        grass: this.texture_loader.load(
          ""
        ),
        floor: this.texture_loader.load(
          ""
        ),
      };

      this.tex.floor.wrapS = this.tex.floor.wrapT = THREE.RepeatWrapping;
      this.tex.floor.repeat.set(20, 20);
    },
    initShaders() {
      // 顶点着色器
      this.vs.sprite = `
        attribute vec3 offset;
        attribute vec2 scale;
        attribute vec4 quaternion;
        attribute float rotation;
        attribute vec4 color;
        attribute float blend;
        attribute float texture;
        uniform float time;
        varying vec2 vUv;
        varying vec4 vColor;
        varying float vBlend;
        varying float num;
        vec3 localUpVector=vec3(0.0,1.0,0.0);

        void main(){
          float angle=time*rotation;
          vec3 vRotated=vec3(position.x*scale.x*cos(angle)-position.y*scale.y*sin(angle),position.y*scale.y*cos(angle)+position.x*scale.x*sin(angle),position.z);

          vUv=uv;
          vColor=color;
          vBlend=blend;
          num=texture;

          vec3 vPosition;
          vec3 vLook=offset-cameraPosition;
          vec3 vRight=normalize(cross(vLook,localUpVector));
          vPosition=vRotated.x*vRight+vRotated.y*localUpVector+vRotated.z;

          gl_Position=projectionMatrix*modelViewMatrix*vec4(vPosition+offset,1.0);
        }
      `;

      // 片元着色器
      this.fs.sprite = `
        const int count=3;
        uniform sampler2D map[count];
        varying vec2 vUv;
        varying vec4 vColor;
        varying float vBlend;
        varying float num;

        void main(){
          if(num==0.0){ gl_FragColor=texture2D(map[0],vUv)*vColor; }
          else if(num==1.0){ gl_FragColor=texture2D(map[1],vUv)*vColor; }
          else if(num==2.0){ gl_FragColor=texture2D(map[2],vUv)*vColor; }

          gl_FragColor.rgb*=gl_FragColor.a;
          gl_FragColor.a*=vBlend;
        }
      `;
    },
    initParticles() {
      // 初始化草地粒子
      this.initGrassParticles();
      // 初始化烟雾发射器
      this.initSmokeEmitters();
    },
    initGrassParticles() {
      // 添加主要草地
      this.particles_grass_a.push({
        offset: [10, 2, 0],
        scale: [4, 4],
        quaternion: [0, 0, 0, 4],
        rotation: 0,
        color: [1, 1, 1, 1],
        blend: 1,
        texture: 2,
      });

      // 添加随机草地
      for (let n = 0; n < 100; n++) {
        const scale = Math.random() * 0.5 + 0.5;
        this.particles_grass_a.push({
          offset: [Math.random() * 20 - 10, scale / 2, Math.random() * 20 - 10],
          scale: [scale, scale],
          quaternion: [0, 0, 0, 4],
          rotation: 0,
          color: [1, 1, 1, 1],
          blend: 1,
          texture: 2,
        });
      }
    },
    initSmokeEmitters() {
      // 初始化烟雾发射器配置
      const emitterConfigs = [
        {
          position: { x: -2, y: 0, z: -4 },
          radius_1: 0.02,
          radius_2: 1,
          radius_height: 5,
          add_time: 0.1,
          elapsed: 0,
          live_time_from: 7,
          live_time_to: 7.5,
          opacity_decrease: 0.008,
          rotation_from: 0.5,
          rotation_to: 1,
          speed_from: 0.005,
          speed_to: 0.01,
          scale_from: 0.2,
          scale_increase: 0.004,
          color_from: [2, 2, 2],
          color_to: [0, 0, 0],
          color_speed_from: 0.4,
          color_speed_to: 0.4,
          brightness_from: 1,
          brightness_to: 1,
          opacity: 1,
          blend: 0.8,
          texture: 1,
        },
        {
          position: { x: 0, y: 0, z: -4 },
          radius_1: 0.02,
          radius_2: 1,
          radius_height: 5,
          add_time: 0.1,
          elapsed: 0,
          live_time_from: 10,
          live_time_to: 10.5,
          opacity_decrease: 0.008,
          rotation_from: 0.5,
          rotation_to: 1,
          speed_from: 0.005,
          speed_to: 0.01,
          scale_from: 0.2,
          scale_increase: 0.004,
          color_from: [0.1, 0.1, 0.1],
          color_to: [0.1, 0.1, 0.1],
          color_speed_from: 1,
          color_speed_to: 1,
          brightness_from: 1,
          brightness_to: 1,
          opacity: 1,
          blend: 1,
          texture: 0,
        },
        {
          position: { x: 2, y: 0, z: -4 },
          radius_1: 0.02,
          radius_2: 0.4,
          radius_height: 5,
          add_time: 0.1,
          elapsed: 0,
          live_time_from: 4,
          live_time_to: 4.5,
          opacity_decrease: 0.004,
          rotation_from: 2,
          rotation_to: 3,
          speed_from: 0.005,
          speed_to: 0.01,
          scale_from: 0.1,
          scale_increase: 0.003,
          color_from: [1, 1, 1],
          color_to: [1, 1, 1],
          color_speed_from: 1,
          color_speed_to: 1,
          brightness_from: 1.0,
          brightness_to: 1,
          opacity: 0.4,
          blend: 0.5,
          texture: 0,
        },
        {
          position: { x: 4, y: 0, z: -4 },
          radius_1: 2,
          radius_2: 2,
          radius_height: 5,
          add_time: 0.1,
          elapsed: 0,
          live_time_from: 1,
          live_time_to: 1.5,
          opacity_decrease: 0.004,
          rotation_from: 2,
          rotation_to: 3,
          speed_from: 0.005,
          speed_to: 0.01,
          scale_from: 0.0,
          scale_increase: 0.003,
          color_from: [1, 2, 1],
          color_to: [1, 1, 2],
          color_speed_from: 1,
          color_speed_to: 1,
          brightness_from: 1.0,
          brightness_to: 1,
          opacity: 0.4,
          blend: 0.7,
          texture: 0,
        },
        {
          position: { x: 0, y: 1, z: 0 },
          radius_1: 0.02,
          radius_2: 1,
          radius_height: 5,
          add_time: 0.01,
          elapsed: 0,
          live_time_from: 1,
          live_time_to: 1.5,
          opacity_decrease: 0.008,
          rotation_from: 0.5,
          rotation_to: 1,
          speed_from: 0.005,
          speed_to: 0.01,
          scale_from: 0.2,
          scale_increase: 0.004,
          color_from: [2, 2, 2],
          color_to: [0, 0, 0],
          color_speed_from: 1,
          color_speed_to: 1,
          brightness_from: 1,
          brightness_to: 1,
          opacity: 1,
          blend: 0.8,
          texture: 1,
        },
      ];

      this.particles_emmiter = emitterConfigs;
    },
    // 发射单个粒子
    emitParticle(item) {
      const radius_1 = item.radius_1 * Math.sqrt(Math.random());
      const theta_1 = 2 * Math.PI * Math.random();
      const x_1 = item.position.x + radius_1 * Math.cos(theta_1);
      const z_1 = item.position.z + radius_1 * Math.sin(theta_1);

      const radius_2 = item.radius_2 * Math.sqrt(Math.random());
      const theta_2 = 2 * Math.PI * Math.random();
      const x_2 = x_1 + radius_2 * Math.cos(theta_2);
      const z_2 = z_1 + radius_2 * Math.sin(theta_2);

      const direction_x = x_2 - x_1;
      const direction_y = item.radius_height;
      const direction_z = z_2 - z_1;

      const speed =
        Math.random() * (item.speed_to - item.speed_from) + item.speed_from;
      const divide =
        (1 /
          Math.sqrt(
            direction_x * direction_x +
              direction_y * direction_y +
              direction_z * direction_z
          )) *
        speed;

      const brightness =
        Math.random() * (item.brightness_to - item.brightness_from) +
        item.brightness_from;

      this.particles_smoke_a.push({
        offset: [x_1, item.position.y, z_1],
        scale: [item.scale_from, item.scale_from],
        quaternion: [
          direction_x * divide,
          direction_y * divide,
          direction_z * divide,
          3,
        ],
        rotation:
          Math.random() * (item.rotation_to - item.rotation_from) +
          item.rotation_from,
        color: [1, 1, 1, item.opacity],
        blend: item.blend,
        texture: item.texture,
        live:
          Math.random() * (item.live_time_to - item.live_time_from) +
          item.live_time_from,
        scale_increase: item.scale_increase,
        opacity_decrease: item.opacity_decrease,
        color_from: [
          item.color_from[0] * brightness,
          item.color_from[1] * brightness,
          item.color_from[2] * brightness,
        ],
        color_to: [
          item.color_to[0] * brightness,
          item.color_to[1] * brightness,
          item.color_to[2] * brightness,
        ],
        color_speed:
          Math.random() * (item.color_speed_to - item.color_speed_from) +
          item.color_speed_from,
        color_pr: 0,
      });
    },
    updateParticleEmitters() {
      // 更新第5个发射器的位置（动态移动）
      const movingEmitter = this.particles_emmiter[4].position;
      movingEmitter.x = Math.sin(this.time / 1) * 4;
      movingEmitter.z = Math.cos(this.time / 1) * 4;

      // 更新所有发射器
      for (let i = 0; i < this.particles_emmiter.length; i++) {
        const emitter = this.particles_emmiter[i];
        let add = 0;

        emitter.elapsed += this.delta;
        add = Math.floor(emitter.elapsed / emitter.add_time);
        emitter.elapsed -= add * emitter.add_time;

        if (add > (0.016 / emitter.add_time) * 60 * 1) {
          emitter.elapsed = 0;
          add = 0;
        }

        while (add--) {
          this.emitParticle(emitter);
        }
      }

      // 更新烟雾粒子
      const max = this.particles_smoke_a.length;
      const alive = [];

      for (let j = 0; j < max; j++) {
        const particle = this.particles_smoke_a[j];

        // 更新颜色
        if (particle.color_pr < 1) {
          const color_r =
            particle.color_from[0] +
            (particle.color_to[0] - particle.color_from[0]) * particle.color_pr;
          const color_g =
            particle.color_from[1] +
            (particle.color_to[1] - particle.color_from[1]) * particle.color_pr;
          const color_b =
            particle.color_from[2] +
            (particle.color_to[2] - particle.color_from[2]) * particle.color_pr;
          particle.color_pr += this.delta * particle.color_speed;
          particle.color[0] = color_r;
          particle.color[1] = color_g;
          particle.color[2] = color_b;
        } else {
          particle.color[0] = particle.color_to[0];
          particle.color[1] = particle.color_to[1];
          particle.color[2] = particle.color_to[2];
        }

        // 更新位置和大小
        particle.offset[0] += particle.quaternion[0] + this.wind.x;
        particle.offset[1] += particle.quaternion[1] + this.wind.y;
        particle.offset[2] += particle.quaternion[2] + this.wind.z;
        particle.scale[0] += particle.scale_increase;
        particle.scale[1] += particle.scale_increase;

        // 更新生命周期
        if (particle.live > 0) {
          particle.live -= this.delta;
        } else {
          particle.color[3] -= particle.opacity_decrease;
        }

        // 保留存活的粒子
        if (particle.color[3] > 0) {
          alive.push(particle);
        }
      }

      this.particles_smoke_a = alive;
    },
    updateParticleAttributes() {
      // 合并烟雾和草地粒子
      this.particles = [...this.particles_smoke_a, ...this.particles_grass_a];

      // 计算到相机的距离并排序
      const cameraPos = this.camera.position;
      this.particles.forEach((particle) => {
        const offset = particle.offset;
        particle.d = Math.sqrt(
          Math.pow(cameraPos.x - offset[0], 2) +
            Math.pow(cameraPos.y - offset[1], 2) +
            Math.pow(cameraPos.z - offset[2], 2)
        );
      });

      this.particles.sort((a, b) => b.d - a.d);

      // 准备缓冲区数据
      const count = this.particles.length;
      const offset = new Float32Array(count * 3);
      const scale = new Float32Array(count * 2);
      const quaternion = new Float32Array(count * 4);
      const rotation = new Float32Array(count);
      const color = new Float32Array(count * 4);
      const blend = new Float32Array(count);
      const texture = new Float32Array(count);

      // 更新粒子属性
      for (let n = 0; n < count; n++) {
        const particle = this.particles[n];

        // 基本属性
        rotation[n] = particle.rotation;
        texture[n] = particle.texture;
        blend[n] = particle.blend;

        // 缩放
        const scaleIndex = n * 2;
        scale[scaleIndex] = particle.scale[0];
        scale[scaleIndex + 1] = particle.scale[1];

        // 位置
        const offsetIndex = n * 3;
        offset[offsetIndex] = particle.offset[0];
        offset[offsetIndex + 1] = particle.offset[1];
        offset[offsetIndex + 2] = particle.offset[2];

        // 颜色和四元数
        const fourIndex = n * 4;
        color[fourIndex] = particle.color[0];
        color[fourIndex + 1] = particle.color[1];
        color[fourIndex + 2] = particle.color[2];
        color[fourIndex + 3] = particle.color[3];
        quaternion[fourIndex] = particle.quaternion[0];
        quaternion[fourIndex + 1] = particle.quaternion[1];
        quaternion[fourIndex + 2] = particle.quaternion[2];
        quaternion[fourIndex + 3] = particle.quaternion[3];
      }

      // 更新几何体属性
      const attributes = this.mesh.sprite.geometry.attributes;
      attributes.offset = new THREE.InstancedBufferAttribute(
        offset,
        3
      ).setUsage(THREE.DynamicDrawUsage);
      attributes.scale = new THREE.InstancedBufferAttribute(scale, 2).setUsage(
        THREE.DynamicDrawUsage
      );
      attributes.quaternion = new THREE.InstancedBufferAttribute(
        quaternion,
        4
      ).setUsage(THREE.DynamicDrawUsage);
      attributes.rotation = new THREE.InstancedBufferAttribute(
        rotation,
        1
      ).setUsage(THREE.DynamicDrawUsage);
      attributes.color = new THREE.InstancedBufferAttribute(color, 4).setUsage(
        THREE.DynamicDrawUsage
      );
      attributes.blend = new THREE.InstancedBufferAttribute(blend, 1).setUsage(
        THREE.DynamicDrawUsage
      );
      attributes.texture = new THREE.InstancedBufferAttribute(
        texture,
        1
      ).setUsage(THREE.DynamicDrawUsage);

      this.mesh.sprite.geometry._maxInstanceCount = count;
    },
    updateParticles() {
      // 更新发射器和粒子状态
      this.updateParticleEmitters();
      // 更新粒子属性和缓冲区
      this.updateParticleAttributes();
    },
    setupScene() {
      // 设置精灵网格
      this.setupSpriteMesh();
      // 设置地板
      this.setupFloor();
      // 添加环境光
      this.scene.add(new THREE.AmbientLight(0xffffff, 3));
    },
    setupSpriteMesh() {
      const geometry = new THREE.InstancedBufferGeometry();
      geometry.setAttribute(
        "position",
        new THREE.Float32BufferAttribute(
          new Float32Array([
            -0.5, 0.5, 0, -0.5, -0.5, 0, 0.5, 0.5, 0, 0.5, -0.5, 0, 0.5, 0.5, 0,
            -0.5, -0.5, 0,
          ]),
          3
        )
      );
      geometry.setAttribute(
        "uv",
        new THREE.Float32BufferAttribute(
          new Float32Array([0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0]),
          2
        )
      );
      geometry.setAttribute(
        "offset",
        new THREE.InstancedBufferAttribute(new Float32Array(), 3)
      );
      geometry.setAttribute(
        "scale",
        new THREE.InstancedBufferAttribute(new Float32Array(), 2)
      );
      geometry.setAttribute(
        "quaternion",
        new THREE.InstancedBufferAttribute(new Float32Array(), 4)
      );
      geometry.setAttribute(
        "rotation",
        new THREE.InstancedBufferAttribute(new Float32Array(), 1)
      );
      geometry.setAttribute(
        "color",
        new THREE.InstancedBufferAttribute(new Float32Array(), 4)
      );
      geometry.setAttribute(
        "blend",
        new THREE.InstancedBufferAttribute(new Float32Array(), 1)
      );
      geometry.setAttribute(
        "texture",
        new THREE.InstancedBufferAttribute(new Float32Array(), 1)
      );

      this.mat.sprite = new THREE.ShaderMaterial({
        uniforms: {
          map: { value: [this.tex.smoke, this.tex.fire, this.tex.grass] },
          time: { value: 0 },
        },
        vertexShader: this.vs.sprite,
        fragmentShader: this.fs.sprite,
        transparent: true,
        depthWrite: false,
        blending: THREE.CustomBlending,
        blendEquation: THREE.AddEquation,
        blendSrc: THREE.OneFactor,
        blendDst: THREE.OneMinusSrcAlphaFactor,
      });

      this.mesh.sprite = new THREE.Mesh(geometry, this.mat.sprite);
      this.mesh.sprite.frustumCulled = false;
      this.mesh.sprite.matrixAutoUpdate = false;
      this.mesh.sprite.updateMatrixWorld = function () {};
      this.scene.add(this.mesh.sprite);
    },
    setupFloor() {
      this.mat.floor = new THREE.MeshStandardMaterial({
        map: this.tex.floor,
        bumpMap: this.tex.floor,
        bumpScale: 0.01,
        metalness: 0.16,
      });

      this.mesh.floor = new THREE.Mesh(
        new THREE.BoxGeometry(20, 1, 20),
        this.mat.floor
      );
      this.mesh.floor.position.y = -0.5;
      this.scene.add(this.mesh.floor);
    },
    animate() {
      requestAnimationFrame(this.animate);
      this.delta = this.clock.getDelta();
      this.time = this.clock.elapsedTime.toFixed(2);

      this.updateParticles();
      this.mat.sprite.uniforms.time.value = this.clock.elapsedTime;
      this.renderer.render(this.scene, this.camera);
    },
    cleanupThreeJS() {
      // 停止动画循环
      if (this.renderer) {
        this.renderer.dispose();
      }
      // 清理材质
      Object.values(this.mat).forEach((material) => {
        if (material.dispose) {
          material.dispose();
        }
      });
      // 清理纹理
      Object.values(this.tex).forEach((texture) => {
        if (texture.dispose) {
          texture.dispose();
        }
      });
      // 清理几何体
      Object.values(this.mesh).forEach((mesh) => {
        if (mesh.geometry && mesh.geometry.dispose) {
          mesh.geometry.dispose();
        }
      });
    },
  },
};
</script>

<style lang="less" scoped>
.three-container {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: #000;
  #box {
    height: 100%;
  }
}
</style>
